from typing import Optional, Dict, Any
import re
import time
import hashlib
import jwt
from datetime import datetime, timedelta
import redis
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import requests
from abc import ABC, abstractmethod

# 数据库配置
Base = declarative_base()
engine = create_engine('sqlite:///social_network.db')
SessionLocal = sessionmaker(bind=engine)

# Redis配置（用于存储验证码和登录尝试次数）
redis_client = redis.Redis(host='localhost', port=6379, db=0)


class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    email = Column(String, unique=True, index=True)
    phone = Column(String, unique=True, index=True)
    password_hash = Column(String)
    wechat_id = Column(String, unique=True)
    last_login = Column(DateTime)

# 登录策略抽象基类


class LoginStrategy(ABC):
    @abstractmethod
    def login(self, **kwargs) -> Optional[Dict[str, Any]]:
        pass


class PasswordLoginStrategy(LoginStrategy):
    def login(self, **kwargs) -> Optional[Dict[str, Any]]:
        username = kwargs.get('username')
        password = kwargs.get('password')

        # 检查登录尝试次数
        attempts_key = f"login_attempts:{username}"
        attempts = int(redis_client.get(attempts_key) or 0)
        if attempts >= 5:
            return {"error": "登录尝试次数过多，请15分钟后再试"}

        db = SessionLocal()
        user = db.query(User).filter(
            (User.username == username) |
            (User.email == username) |
            (User.phone == username)
        ).first()

        if not user or not verify_password(password, user.password_hash):
            redis_client.incr(attempts_key)
            redis_client.expire(attempts_key, 900)  # 15分钟过期
            return {"error": "用户名或密码错误"}

        return self._generate_login_response(user)


class PhoneLoginStrategy(LoginStrategy):
    def login(self, **kwargs) -> Optional[Dict[str, Any]]:
        phone = kwargs.get('phone')
        code = kwargs.get('code')

        # 验证手机验证码
        stored_code = redis_client.get(f"sms_code:{phone}")
        if not stored_code or stored_code.decode() != code:
            return {"error": "验证码错误或已过期"}

        db = SessionLocal()
        user = db.query(User).filter(User.phone == phone).first()
        if not user:
            return {"error": "用户不存在"}

        return self._generate_login_response(user)


class WeChatLoginStrategy(LoginStrategy):
    def login(self, **kwargs) -> Optional[Dict[str, Any]]:
        code = kwargs.get('code')

        # 微信登录配置
        WECHAT_APP_ID = "your_app_id"
        WECHAT_APP_SECRET = "your_app_secret"

        # 获取微信access_token
        url = f"https://api.weixin.qq.com/sns/oauth2/access_token?appid={WECHAT_APP_ID}&secret={WECHAT_APP_SECRET}&code={code}&grant_type=authorization_code"
        response = requests.get(url)
        if response.status_code != 200:
            return {"error": "微信登录失败"}

        data = response.json()
        openid = data.get('openid')

        db = SessionLocal()
        user = db.query(User).filter(User.wechat_id == openid).first()
        if not user:
            return {"error": "未绑定微信账号"}

        return self._generate_login_response(user)


class LoginManager:
    def __init__(self):
        self.strategies = {
            'password': PasswordLoginStrategy(),
            'phone': PhoneLoginStrategy(),
            'wechat': WeChatLoginStrategy()
        }

    def login(self, login_type: str, **kwargs) -> Dict[str, Any]:
        strategy = self.strategies.get(login_type)
        if not strategy:
            return {"error": "不支持的登录方式"}

        result = strategy.login(**kwargs)
        if "error" not in result:
            self._update_login_time(kwargs.get('username'))
        return result

    def _update_login_time(self, username: str):
        db = SessionLocal()
        user = db.query(User).filter(User.username == username).first()
        if user:
            user.last_login = datetime.utcnow()
            db.commit()
        db.close()

# 工具函数


def verify_password(plain_password: str, hashed_password: str) -> bool:
    return hashlib.sha256(plain_password.encode()).hexdigest() == hashed_password


def generate_token(user_id: int) -> str:
    payload = {
        'user_id': user_id,
        'exp': datetime.utcnow() + timedelta(hours=24)
    }
    return jwt.encode(payload, 'your_secret_key', algorithm='HS256')


def _generate_login_response(user) -> Dict[str, Any]:
    token = generate_token(user.id)
    return {
        "token": token,
        "user_id": user.id,
        "username": user.username,
        "expires_in": 24 * 3600  # 24小时过期
    }


# 使用示例
if __name__ == "__main__":
    # 创建数据库表
    Base.metadata.create_all(bind=engine)

    login_manager = LoginManager()

    # 密码登录示例
    result = login_manager.login(
        login_type='password',
        username='test@example.com',
        password='password123'
    )
    print(result)

    # 手机验证码登录示例
    result = login_manager.login(
        login_type='phone',
        phone='13800138000',
        code='123456'
    )
    print(result)

    # 微信登录示例
    result = login_manager.login(
        login_type='wechat',
        code='wx_auth_code'
    )
    print(result)
